$LOAD_PATH << './lib'
require 'rubygems'
require 'rexml/document'
require 'cgi'
include PNEngine

#	== Class PNMLSyntaxException
#
#	Class to provide detailed information about PNML syntax erros.
class PNMLSyntaxException < Exception
  attr_reader :msg

  # Constructor. A message containing a detailed error description should be added.
  def initialize(msg)
    @msg = msg
  end

  def to_s
    @msg
  end
end

#	== Class PNMLParser
#	
#	This class provides methods to parse PNML files and
#	automatically create petrinets.
class PNMLParser

  #	Parses the specified PNML.
  #	Parameter can be IO Object, PNML String or REXML::Document.
  #
  #	Throws #PNMLSyntaxException on errors in the PNML.
  #	Returns the created petrinet.	
  def self.parse(param)
    petrinet = nil

    #use xmlsimple to get hash
    pnml = REXML::Document.new(param)
    petrinet = create_petrinet(pnml)
		
    petrinet
  end

  private
	
  #	This creates a petrinet described through the specified pnml document
  #	Returns the created petrinet.
  def self.create_petrinet(pnml)    
    pnet = nil
    pnet = pnml.root.get_elements('./net').first

    if pnet
      #create new petri net
      petrinet = Petrinet.new

      petrinet.pid = pnet.attribute('id').to_s

      #get required child name
      petrinet.name = get_value_for_attr('name', pnet)
	
      #add roles to petrinet
      toolspecific = pnet.get_elements('./toolspecific').first
      if toolspecific
        
        role = toolspecific.get_elements('./role')
        
        role.each do |x|
            
          rolename = x.attribute('name')
          if rolename
            rolename = CGI::unescapeHTML(rolename.to_s)
          end
          
          trInitProcess = x.attribute('trInitProcess')
          if trInitProcess
            trInitProcess = CGI::unescapeHTML(trInitProcess.to_s)
          end
          
          trAllocate = x.attribute('trAllocate')
          if trAllocate
            trAllocate = CGI::unescapeHTML(trAllocate.to_s)
          end
          
          trSuspend = x.attribute('trSuspend')
          if trSuspend
            trSuspend = CGI::unescapeHTML(trSuspend.to_s)  
          end  
        
          trResume = x.attribute('trResume')
          if trResume
            trResume = CGI::unescapeHTML(trResume.to_s)       
          end  
        
          trSubmit = x.attribute('trSubmit')
          if trSubmit
            trSubmit = CGI::unescapeHTML(trSubmit.to_s)              
          end  
        
          trDelegate = x.attribute('trDelegate')
          if trDelegate
            trDelegate = CGI::unescapeHTML(trDelegate.to_s)              
          end  
          
          trReview = x.attribute('trReview')
          if trReview
            trReview = CGI::unescapeHTML(trReview.to_s)              
          end  
         
          trSkip = x.attribute('trSkip')
          if trSkip
            trSkip = CGI::unescapeHTML(trSkip.to_s)              
          end  
          
          
          #check whether the role exist 
          exist = Role.find(:first, :conditions => ["name = ?", rolename])
          #if there is no entry, create a new one
          if !exist
            role_entry = Role.new
            role_entry.name = rolename
            role_entry.tr_initProcess = trInitProcess
            role_entry.tr_allocate = trAllocate
            role_entry.tr_suspend = trSuspend
            role_entry.tr_resume = trResume
            role_entry.tr_submit = trSubmit
            role_entry.tr_delegate = trDelegate
            role_entry.tr_review = trReview
            role_entry.tr_skip = trSkip        
            role_entry.save!  
          end
         end         
      end
      
      #add places to petrinet
      place_idhash = get_place_idhash(pnet)
      if place_idhash.size > 0
        place_idhash.each do |id, place|
          petrinet.places << place
        end
      end
						
      #add transitions to petrinet
      transition_idhash = get_transition_idhash(pnet)
      if transition_idhash.size > 0
        transition_idhash.each do |id, transition|
          petrinet.transitions << transition
        end
      end

      #add arcs to petrinet
      arcs = create_arcs(pnet.get_elements('./arc'), place_idhash.merge(transition_idhash))
      if arcs.size > 0	
        arcs.each do |arc|
          petrinet.arcs << arc
        end
      end
	
      petrinet.save!
    else
      raise PNMLSyntaxException.new('Invalid PNML file. No <net> tag found.')
    end
    petrinet
  end

  #	Private constructor.
  #	There should be no instances of PNMLParser.
  def initialize
  end	

  #	This method adds creates arcs.
  #	<b>parcs<b> is an array of REXML::Elements
  # 	<b>place_idhash<b> is a hash of the place and transitions objects with the related id as key.
  def self.create_arcs(parcs, idhash)
    arcs = []
    parcs.each do |parc|

      sourceid = parc.attribute('source').to_s
      targetid = parc.attribute('target').to_s
      arc_type = parc.attribute('type').to_s

      if idhash[targetid] && idhash[sourceid]
        if arc_type == 'read'
          arc = ReadArc.new(idhash[sourceid], idhash[targetid])
        else
          arc = Arc.new(idhash[sourceid], idhash[targetid])
        end

        arc.pid = parc.attribute('id').to_s

        #look for optional child toolspecific
        toolspecific = parc.get_elements('./toolspecific').first
        if toolspecific
          #look for optional child inscription
          transformation = toolspecific.get_elements('./transformation').first
          if transformation
            href = transformation.attribute('href').to_s
            if href
              arc.xslt_url = href unless href.empty?
            end
          end
        end


        arcs << arc
      else
        raise PNMLSyntaxException.new('Arc has either no target or no source defined')
      end
    end

    arcs
  end

  def self.get_idhash(tag_name, pnet)
    idhash = {}
    if pnet
      pnet.get_elements("./#{tag_name}").each do |tag|
        method_name = "create_" + tag_name
        tag_obj = eval %Q{#{method_name}(tag)}

        #use id as key
        if tag.attribute('id')
          idhash[tag.attribute('id').to_s] = tag_obj
        else
          raise PNMLSyntaxException.new('An tag ' + tag_name + ' does not have an id.')
        end
      end
    end
    idhash
  end

  def self.get_place_idhash(pnet)
    get_idhash('place', pnet)
  end

  def self.get_transition_idhash(pnet)
    get_idhash('transition', pnet)
  end

  #attribute values are stored like
  #<any attribute>
  #	<value> some value </value>
  #</any attribute>
  #this method returns the value for the specified attribute
  def self.get_value_for_attr(attr_name, pnml)
    value = ""
    if (pnml && attr_name)
      #get attribute hash
      attr = pnml.get_elements("./#{attr_name}").first
      if attr
        #check for value tag
        value_tag = attr.get_elements('./text')
        if (value_tag.size > 0)
          value = value_tag[0].text
        else
          raise PNMLSyntaxException.new(attr_name + ' has no value tag.')
        end
      end
    end
    value
  end

  def self.create_transition(ptransition)
    transition = nil
    if ptransition
      transition = case ptransition.attribute('type').to_s
      when 'receive'
        ReceiveTransition.new
      when 'send'
        SendTransition.new
      when 'automatic'
        AutomaticTransition.new
      when 'referer'
        RefererTransition.new
      else
        AutomaticTransition.new
      end
		
      #look for optional child name 
      begin
        name = get_value_for_attr('name', ptransition)
        name = name.nil? ? "noname" : name
        transition.name = name.empty? ? "noname" : name
      rescue PNMLSyntaxException => ex
        p ex
      end

      transition.pid = ptransition.attribute('id').to_s

      #look for optional child toolspecific
      toolspecific = ptransition.get_elements('./toolspecific').first
      if toolspecific
        begin
        #look for optional chfild automaticfire
        fire = toolspecific.get_elements('./fire').first
        if fire
          #look for optional attribute href
          href = fire.attribute('href').to_s
          if href
            transition.xslt_url = href unless href.empty?
          end
        end
        
        #look for sendTo href or locator
        send_to = toolspecific.get_elements('./sendTo').first
        if send_to
          transition.send_to_locator = send_to.attribute('locator').to_s
          transition.send_to = send_to.attribute('href').to_s
        end
		
        #look for redirectTo xpath expression and transition pid (only for Send Transitions)
        redirect_to = toolspecific.get_elements('./redirectTo').first
        if redirect_to
          transition.redirect_to_locator = redirect_to.attribute('locator').to_s
          transition.redirect_to_transition = redirect_to.attribute('transitionID').to_s
        end
        
        #look for role attribute
        role = toolspecific.get_elements('./role').first
        if role
          role = CGI::unescapeHTML(role.text)
          transition.role = role   
        end
        
          #look for role attribute
        owner = toolspecific.get_elements('./owner').first
        if owner
          owner = CGI::unescapeHTML(owner.text)
          transition.owner = owner   
        end
        
        #look for contextPlaceID
        contextPlaceID = toolspecific.get_elements('./contextPlaceID').first
        if contextPlaceID
          contextPlaceID = CGI::unescapeHTML(contextPlaceID.text)
          transition.context_place_id = contextPlaceID
        end
        
        #look for optional child guard
        guard = toolspecific.get_elements('./guard').first
        if guard
          expr = guard.get_elements('./expr').first
          if expr
            expr = CGI::unescapeHTML(expr.text)
            transition.guard = expr
          end
        end
        
        #look for optional child refer
        refer = toolspecific.get_elements('./refer').first
        if refer
          type = refer.attribute('type').to_s

          unless type == 'xpath' || type == 'url' || type == 'locator'
            raise PNMLSyntaxException.new('Type of refer can only be "xpath" or "url" or "locator"')
          end
          transition.refer_type = type if type
          href = refer.attribute('href').to_s
          transition.refer_to = href
        end
		
		#look for optional child location (for send transitions: where to write Location URL got back from receive transition)
		transition.location_locator = toolspecific.get_elements('./location').first.attribute('href').to_s rescue nil
              
        #look for worklist properties
        worklist = toolspecific.get_elements('./worklist').first
        if worklist
          action = worklist.attribute('action').to_s
          if action && !action.empty?            
            transition.action = action
            # only change task name if transition 
            task_name = worklist.attribute('task').to_s
            if task_name
              transition.name = task_name unless task_name.empty?
            end
          end          
        end
      
        #look for optional output
        transition.output_model = Helpers.load_from_url(toolspecific.get_elements('./output/model').first.attribute("href").to_s) rescue nil
        transition.output_form = Helpers.load_from_url(toolspecific.get_elements('./output/form').first.attribute("href").to_s) rescue nil
        transition.output_bindings = Helpers.load_from_url(toolspecific.get_elements('./output/bindings').first.attribute("href").to_s) rescue nil
        rescue Exception => ex
          puts ex
        end
      end
    end
    transition
  end

  private	
  def self.create_place(pplace)
    place = nil
    if pplace	
      
      
        
      #look for optional child toolspecific
      toolspecific = pplace.get_elements('./toolspecific').first
      if toolspecific
        
        #look for optional child external
        external = toolspecific.get_elements('./external').first
        if external
          #look for mandantory child type
          place = case external.attribute('type').to_s
          when "pull": PullPlace.new
          when "push": PushPlace.new
          when "beam": BeamPlace.new
          else
            raise PNMLSyntaxException.new('External Type should be "push" or "pull"')
          end

          #look for optional child link
          href = external.attribute('href').to_s
          if href
            place.feed_url = href unless href.empty?
          end
        end

        place = Place.new unless place
        place.contenttype = pplace.attribute('type').to_s
        if place.contenttype == "data"
          place.modelxml = pplace.get_elements('./model').first.get_text.value.to_s
        else
          place.modelxml = nil
        end
								
        #look for optional child locator
        locators = toolspecific.get_elements('./locator')
        locators.each do |locator|
          #get required childs name, type, expr
          name = locator.get_elements('./name').first.text
          type = locator.get_elements('./type').first.text
          expr = CGI::unescapeHTML(locator.get_elements('./expr').first.text)

          place.locators << Locator.new(:name => name, :expr => expr, :val_type => type)
        end
      end			

      place = Place.new unless place	

      #store pnml id
      place.pid = pplace.attribute('id').to_s

      #look for optional child name
      begin
        place.name = get_value_for_attr('name', pplace)
      rescue PNMLSyntaxException => ex
        p ex
      end
			
    end
    place
  end
  
  def self.b(&block)
	res = Benchmark.measure{yield}
	puts res
	res
  end
end
